
from fastapi import APIRouter,HTTPException, Depends,UploadFile,Form,File
from app.services.doi_parser import DOIParser 
from app.models.schemas import DOIResponse, DOIInputRequest,DOITask,DOIItem,JiangSuInputRequest,YLQXBNInputRequest,YLQXBNDetailInputRequest
from app.services.db_service_factory import get_db_service 
from app.models.database import TaskDB,DOIItemDB
from arq import ArqRedis
from app.core.arq_app import create_arq_pool
from typing import List, Any
import io
import xlsxwriter
from fastapi.responses import StreamingResponse
from app.core.config import settings
import os
from app.services.file_service import file_service

router = APIRouter()

db_service=get_db_service()
# 依赖项：获取ARQ Redis连接
async def get_arq_redis() -> ArqRedis:
    redis = await create_arq_pool()
    try:
        yield redis
    finally:
        await redis.close()

@router.post("/input", response_model=DOIResponse[DOITask])
async def input_dois(request: DOIInputRequest, arq_redis: ArqRedis = Depends(get_arq_redis)):
    """
    处理用户手动输入的DOI列表
    
    此接口接收用户手动输入的DOI列表，解析并提交到后台进行处理。
    
    参数:
        request (DOIInputRequest): 包含DOI列表和客户端ID的请求对象 
        
    返回:
        DOIResponse: 包含任务信息和DOI列表的响应对象
        
    错误:
        400: 解析DOI过程中发生错误
    """
    # 生成任务ID
    task_id = DOIParser.generate_task_id()
    try:
        # 解析输入的DOI
        dois_text = request.dois.strip()
        dois = [line.strip() for line in dois_text.splitlines()]
        # 过滤掉空行
        dois = [doi for doi in dois if doi]
         
        # 初始化任务状态
        data = await DOIParser.add_task(task_id, request.client_id, "手动输入", dois)
        
 
        # 在upload_file方法中也做相同修改
        data["dois"] = []
        for index, doi in enumerate(dois):
            item = DOIItem(
                task_id=task_id,
                doi=doi,
                authors=[],
                year="",
                status="pending",
                title="",
                journal="",
                abstract="",
                pdf_url="",
                original_index=index
            )
            data["dois"].append(item.model_dump())
            await DOIParser.add_doi_metater(task_id, doi, item.model_dump(), index=index)
        
        # 使用ARQ启动任务
        job = await arq_redis.enqueue_job('process_doi_list', task_id, dois)
        
        # 保存ARQ任务ID
        #redis_service.update(task_id, {"arq_job_id": job.job_id})
        db_service.save_entity(TaskDB,{"arq_job_id": job.job_id},task_id)

        
        return DOIResponse(
            message=f"成功解析 {len(dois)} 个DOI，任务已提交到后台处理",
            status="success",
            data=data
        )
    except Exception as e: 
        await DOIParser.update_task_status(task_id, {"status":"failed"})
        raise HTTPException(status_code=400, detail=str(e))

@router.post("/upload", response_model=DOIResponse[DOITask])
async def upload_file(
    file:UploadFile = File(..., description="支持 txt、xlsx、xls 格式"),
    client_id:str= Form(None, description="客户端 ID"),
    arq_redis: ArqRedis = Depends(get_arq_redis)
): 
    """
    处理用户上传的文件中的DOI列表
    
    此接口接收用户上传的TXT或Excel文件，解析其中的DOI列表并提交到后台进行处理。
    如果是Excel文件，会将源文件保存到UPLOAD_DIR目录，以便后续导出时使用。
    
    参数:
        file (UploadFile): 用户上传的文件，支持txt、xlsx、xls格式
        client_id (str): 客户端ID，用于标识用户
        
    返回:
        DOIResponse: 包含任务信息和DOI列表的响应对象
        
    错误:
        400: 不支持的文件类型或解析DOI过程中发生错误
    """ 
    # 获取文件扩展名
    file_ext = file.filename.split('.')[-1].lower()
    if file_ext not in ['txt', 'xlsx', 'xls']:
        raise HTTPException(status_code=400, detail="不支持的文件类型")
    # 生成任务ID
    task_id = DOIParser.generate_task_id()
    try:
        # 直接读取文件内容，不保存到磁盘
        content = await file.read()
        
        # 解析文件内容
        dois = DOIParser.process_file_content(content, file_ext)   
        
        if len(dois)==0:
            raise HTTPException(status_code=400, detail="没有检测到doi")
        # 初始化任务状态
        data = await DOIParser.add_task(task_id, client_id, file.filename, dois)
        
        # 如果是Excel文件，保存源文件到UPLOAD_DIR目录
        if file_ext in ['xlsx', 'xls']:
 
            # 创建目录结构
            upload_dir = os.path.join(settings.UPLOAD_DIR, client_id,"DOI", task_id)
            os.makedirs(upload_dir, exist_ok=True)
            
            # 保存源文件
            source_file_path = os.path.join(upload_dir, file.filename)
            with open(source_file_path, 'wb') as f:
                # 重新定位到文件开头
                await file.seek(0)
                # 重新读取文件内容并写入
                f.write(await file.read())
            
            # 将源文件路径保存到任务状态中
            #redis_service.update(task_id, {"source_file_path": source_file_path})
            db_service.save_entity(TaskDB,{"source_file_path": source_file_path},task_id)

        
        # 在upload_file方法中的相应位置
        data["dois"] = []
        for index, doi in enumerate(dois):
            item = DOIItem(
                task_id=task_id,
                doi=doi,
                authors=[],
                year="",
                status="pending",
                title="",
                journal="",
                abstract="",
                pdf_url="",
                original_index=index
            )
            data["dois"].append(item.model_dump())
            await DOIParser.add_doi_metater(task_id, doi, item.model_dump(), index=index)
        
        # 使用ARQ启动任务
        job = await arq_redis.enqueue_job('process_doi_list', task_id, dois)
        
        # 保存ARQ任务ID
        #redis_service.update(task_id, {"arq_job_id": job.job_id})
        db_service.save_entity(TaskDB,{"arq_job_id": job.job_id},task_id)

 
        return DOIResponse( 
            message=f"成功解析 {len(dois)} 个DOI，任务已提交到后台处理",
            status="success", 
            data=data
        )
    except Exception as e:
        print(str(e))
        await DOIParser.update_task_status(task_id, {"status":"failed"})
        raise HTTPException(status_code=400, detail=str(e))


@router.post("/jiangsu", response_model=DOIResponse[DOITask])
async def jiangsu(request:JiangSuInputRequest,
    arq_redis: ArqRedis = Depends(get_arq_redis)):
    """
    处理江苏省药品监督管理局网站数据爬取任务
    
    此接口接收用户输入的URL，爬取江苏省药品监督管理局网站上的数据。
    
    参数:
        request (JiangSuInputRequest): 包含URL和客户端ID的请求对象
        
    返回:
        DOIResponse: 包含任务信息的响应对象
        
    错误:
        400: 处理过程中发生错误
    """
    task_id = DOIParser.generate_task_id()
    try:
        # 初始化任务状态
        data = await DOIParser.add_task(task_id, request.client_id, request.url, [], type="JiangSu")
        
        # 使用ARQ启动任务
        job = await arq_redis.enqueue_job('process_jiangsu', task_id, request.url)
        
        # 保存ARQ任务ID
        db_service.save_entity(TaskDB, {"arq_job_id": job.job_id}, task_id)
        
        return DOIResponse(
            message=f"任务已提交到后台处理，等待解析",
            status="success",
            data=data
        )
    except Exception as e:
        await DOIParser.update_task_status(task_id, {"status":"failed"})
        raise HTTPException(status_code=400, detail=str(e))

@router.post("/ylqx-bn", response_model=DOIResponse[DOITask])
async def ylqx_bn(request: YLQXBNInputRequest,
    arq_redis: ArqRedis = Depends(get_arq_redis)):
    """
    处理医疗器械备案信息爬取任务
    
    此接口接收用户输入的年份，爬取国家药监局网站上的医疗器械备案信息。
    
    参数:
        request (YLQXBNInputRequest): 包含年份和客户端ID的请求对象
        
    返回:
        DOIResponse: 包含任务信息的响应对象
        
    错误:
        400: 处理过程中发生错误
    """
    task_id = DOIParser.generate_task_id()
    try:
        # 初始化任务状态
        data = await DOIParser.add_task(task_id, request.client_id, f"医疗器械备案信息_{request.year}", [], type="YLQXBN")
        
        # 使用ARQ启动任务
        job = await arq_redis.enqueue_job('process_ylqx_bn', task_id, request.year,request.number,request.type,request.category,request.url,request.page,request.is_single_page)


        
        # 保存ARQ任务ID
        db_service.save_entity(TaskDB, {"arq_job_id": job.job_id}, task_id)
        
        return DOIResponse(
            message=f"医疗器械备案信息爬取任务已提交到后台处理，年份：{request.year}",
            status="success",
            data=data
        )
    except Exception as e:
        await DOIParser.update_task_status(task_id, {"status":"failed"})
        raise HTTPException(status_code=400, detail=str(e))
    

@router.post("/ylqx-bn-detail", response_model=DOIResponse[DOITask])
async def ylqx_bn_detail(request: YLQXBNDetailInputRequest,
    arq_redis: ArqRedis = Depends(get_arq_redis)):
    """
    处理医疗器械备案详细信息爬取任务
     
    
    参数:
        request (YLQXBNDetailInputRequest): URL
        
    返回:
        DOIResponse: 包含任务信息的响应对象
        
    错误:
        400: 处理过程中发生错误
    """
    task_id = DOIParser.generate_task_id()
    try:
        # 初始化任务状态
        data = await DOIParser.add_task(task_id, request.client_id, f"医疗器械备案信息详情_{request.url}", [], type="YLQXBN_DETAIL")
        
        # 使用ARQ启动任务
        job = await arq_redis.enqueue_job('process_ylqx_bn_detail', task_id, request.url)
 
        # 保存ARQ任务ID
        db_service.save_entity(TaskDB, {"arq_job_id": job.job_id}, task_id)
        
        return DOIResponse(
            message=f"医疗器械备案详细信息爬取任务已提交到后台处理",
            status="success",
            data=data
        )
    except Exception as e:
        await DOIParser.update_task_status(task_id, {"status":"failed"})
        raise HTTPException(status_code=400, detail=str(e))
    

 
@router.delete("/task/{task_id}",response_model=DOIResponse[Any])
async def delete_task(task_id: str):
    """
    删除指定的DOI下载任务
    
    此接口删除指定任务ID的DOI下载任务，包括任务状态、DOI列表、下载文件和源文件。
    
    参数:
        task_id (str): 任务ID
        
    返回:
        dict: 包含删除状态和消息的字典
        
    错误:
        404: 任务不存在
        500: 删除任务失败
    """
    # 检查任务是否存在
    #status = redis_service.get(task_id)
    status = db_service.get_entity_as_dict(TaskDB,task_id)
    if not status:
        raise HTTPException(status_code=404, detail="任务不存在")
    
    # 删除任务
    #success = redis_service.delete(task_id)
    success = db_service.delete_entity(TaskDB,task_id)
    file_service.delete_task_files(task_id,status.get("client_id"))
    
    if success:
        return DOIResponse(
            status="success",
            message=f"任务 {task_id} 已成功删除",
            data=None  # 添加data字段，设置为None
        )
    else:
        raise HTTPException(status_code=500, detail="删除任务失败")

@router.delete("/tasks/client/{client_id}",response_model=DOIResponse[Any])
async def delete_all_client_tasks(client_id: str,task_type:str="DOI"):

    """
    删除指定客户端的所有DOI下载任务
    
    此接口删除指定客户端ID的所有DOI下载任务，包括任务状态、DOI列表、下载文件和源文件。
    
    参数:
        client_id (str): 客户端ID
        
    返回:
        dict: 包含删除状态和消息的字典
        
    错误:
        500: 删除任务失败
    """
    # 删除任务
    #success = redis_service.delete_all_tasks_by_client(client_id)
    success = db_service.delete_entities_by_filter(TaskDB,{"client_id":client_id,"task_type":task_type}) 
    file_service.delete_client_files(client_id)
    
    if success:
        return DOIResponse(
            status="success",
            message=f"所有任务已成功删除",
            data=None  # 添加data字段，设置为None
        )
    else:
        raise HTTPException(status_code=500, detail="删除任务失败")


@router.get("/tasks/client/{client_id}",response_model=DOIResponse[List[DOITask]])
async def get_client_tasks(client_id: str,task_type:str="DOI"):
    """
    获取指定客户端的所有DOI下载任务
    
    此接口返回指定客户端ID的所有DOI下载任务，包括任务状态和DOI详情。
    
    参数:
        client_id (str): 客户端ID
        
    返回:
        dict: 包含任务列表的字典
    """
    #tasks = redis_service.get_tasks_by_client_id(client_id)
    tasks = db_service.query_entities_as_dicts(TaskDB,{"client_id":client_id,"task_type":task_type})
    data: List[DOITask] = []
 
    for task in tasks:
        # 创建空的DOITask对象
        task_item = DOITask(
            task_id=str(task.get("task_id", "")),
            client_id=str(task.get("client_id", "")),
            status=str(task.get("status", "pending")),
            total_dois=str(task.get("total_dois", 0)),
            success_dois=str(task.get("success_dois", 0)),
            completed_dois=str(task.get("completed_dois", 0)),
            failed_dois=str(task.get("failed_dois", 0)),
            start_time=str(task.get("start_time", "")),
            end_time=str(task.get("end_time", "")),
            file_name=str(task.get("file_name", "")),
            task_type=str(task.get("task_type", "DOI")), 
        )
        
        # task_item.download_url=settings.BACKEND_SERVER+settings.API_V1_STR + f"/task/{task_item.task_id}/download-pdfs" if task_item.status=="completed" and task_item.success_dois>0 else ""
        
        data.append(task_item)
        task_id = task.get("task_id")
        if task_id:
            pattern = f"doi_task:doi_item:{task_id}:*"
            #items = redis_service.get_items_by_pattern(pattern)
            items = db_service.query_entities_as_dicts(DOIItemDB,{"task_id":task_id})
            task["dois"] = sorted(items, key=lambda item: item.get("original_index", float('inf')))
            
            # 确保每个DOI项目都有所有必需的字段
            dois_items = []  # 正确初始化为空列表
            for item in task["dois"]: 
                # 确保year字段是字符串类型
                # if "year" in item and not isinstance(item["year"], str):
                #     item["year"] = str(item["year"]) 
                
                # 逐个属性赋值创建DOIItem对象
                doi_item = DOIItem(
                    task_id=str(item.get("task_id", "")),
                    doi=str(item.get("doi", "")),
                    authors=item.get("authors", []) or [],  # 确保当authors为None时使用空列表
                    year=str(item.get("year", "")),
                    status=str(item.get("status", "pending")),
                    title=str(item.get("title", "")),
                    journal=str(item.get("journal", "")),
                    abstract=str(item.get("abstract", "")),
                    pdf_url=str(item.get("pdf_url", "")),
                    original_index=item.get("original_index", 0)
                )
                dois_items.append(doi_item)  
 
            task_item.dois = dois_items  # 使用属性赋值而不是字典赋值
    
    return DOIResponse(
        status="success",
        message="任务列表获取成功",
        data=data
    )


@router.get("/task/{task_id}/export")
async def export_task_results(task_id: str):
    # 检查任务是否存在
    #status = redis_service.get(task_id)
    status = db_service.get_entity_as_dict(TaskDB,task_id)
    if not status:
        raise HTTPException(status_code=404, detail="任务不存在")
    
    # 获取DOI列表
    pattern = f"doi_task:doi_item:{task_id}:*"
    #items = redis_service.get_items_by_pattern(pattern)
    items = db_service.query_entities_as_dicts(DOIItemDB,{"task_id":task_id})
    
    # 按original_index排序
    items.sort(key=lambda item: item.get("original_index", float('inf')))
    
    # 创建一个内存中的Excel文件
    output = io.BytesIO()
    workbook = xlsxwriter.Workbook(output)
    worksheet = workbook.add_worksheet("Sheet1")
    
    # 添加格式
    header_format = workbook.add_format({
        'bold': True,
        'align': 'center',
        'valign': 'vcenter',
        'fg_color': '#D7E4BC',
        'border': 1
    })
    
    # 定义状态文本的不同格式
    success_format = workbook.add_format({
        'color': 'green',  # 绿色字体
        'border': 1
    })
    
    error_format = workbook.add_format({
        'color': 'red',  # 红色字体
        'border': 1
    })
    
    normal_format = workbook.add_format({
        'border': 1
    })
    
    # 为摘要列创建带有文本自动换行的格式
    abstract_format = workbook.add_format({
        'border': 1,
        'text_wrap': True,  # 启用文本自动换行
        'valign': 'top'     # 文本顶部对齐
    })
    
    # 写入表头
    headers = ["DOI", "标题", "作者", "期刊", "年份", "摘要", "下载状态", "网络地址"]
    for col, header in enumerate(headers):
        worksheet.write(0, col, header, header_format)
    
    # 设置固定行高 - 为表头行设置
    worksheet.set_row(0, 20)  # 设置表头行高为20
    
    # 写入数据
    for row, item in enumerate(items, start=1):
        # 为每一行设置固定行高
        worksheet.set_row(row, 20)  # 设置数据行高为60，您可以根据需要调整这个值
        
        # 写入数据（除状态列外都使用普通格式）
        worksheet.write(row, 0, item.get("doi", ""), normal_format)
        worksheet.write(row, 1, item.get("title", ""), normal_format)
        
        # 处理作者列 - 如果是列表则转换为字符串
        authors = item.get("authors", [])
        if isinstance(authors, list):
            authors_text = ", ".join(authors)
        else:
            authors_text = str(authors) if authors else ""
        worksheet.write(row, 2, authors_text, normal_format)
        
        worksheet.write(row, 3, item.get("journal", ""), normal_format)
        worksheet.write(row, 4, item.get("year", ""), normal_format)
        
        # 使用带有文本自动换行的格式写入摘要
        worksheet.write(row, 5, item.get("abstract", ""), abstract_format)
        
        # 状态转换为中文并根据状态使用不同颜色
        status_text = "等待中"
        status_format = normal_format
        
        if item.get("status") == "success":
            status_text = "成功"
            status_format = success_format
        elif item.get("status") == "error":
            status_text = "失败"
            status_format = error_format
        elif item.get("status") == "downloading":
            status_text = "下载中"
        
        # 使用对应的格式写入状态
        worksheet.write(row, 6, status_text, status_format)
        
        # 添加网络地址列，如果有pdf_url则添加可点击的链接
        pdf_url = item.get("pdf_url", "")
        if pdf_url:
            # 创建带有超链接的格式
            url_format = workbook.add_format({
                'border': 1,
                'color': 'blue',
                'underline': 1
            })
            # 写入带有超链接的文本
            worksheet.write_url(row, 7, pdf_url, url_format, string="点击打开")
        else:
            worksheet.write(row, 7, "", normal_format)
    
    # 调整列宽
    worksheet.set_column(0, 0, 30)  # DOI列
    worksheet.set_column(1, 1, 40)  # 标题列
    worksheet.set_column(2, 2, 30)  # 作者列
    worksheet.set_column(3, 3, 20)  # 期刊列
    worksheet.set_column(4, 4, 10)  # 年份列
    worksheet.set_column(5, 5, 50)  # 摘要列
    worksheet.set_column(6, 6, 10)  # 状态列
    worksheet.set_column(7, 7, 15)  # 网络地址列
    
    # 关闭工作簿
    workbook.close()
    
    # 将指针移到文件开头
    output.seek(0)
    
    # 返回Excel文件 - 修复编码问题
    import urllib.parse
    filename = f"下载结果_{task_id}.xlsx"
    encoded_filename = urllib.parse.quote(filename)
    return StreamingResponse(
        output,
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}"}
    )


@router.get("/task/{task_id}/export-to-source")
async def export_to_source_file(task_id: str):
    """
    将DOI下载结果追加到原始Excel文件中
    
    此接口用于处理用户上传的Excel文件，在原文件中追加或更新下载状态列和网络地址列。
    - 如果原文件中已有"状态"列和"网络地址"列，则更新这些列的内容
    - 如果原文件中没有这些列，则按顺序追加这些列
    
    参数:
        task_id (str): 任务ID，用于获取任务信息和DOI下载结果
        
    返回:
        StreamingResponse: 返回修改后的Excel文件流
        
    错误:
        404: 任务不存在或源文件不存在
        400: 非Excel文件上传的任务或源文件中未找到DOI列
        500: 导出过程中发生错误
    """
    # 获取任务信息
    #task_info = redis_service.get(task_id)
    task_info = db_service.get_entity_as_dict(TaskDB,task_id)
    if not task_info:
        raise HTTPException(status_code=404, detail="任务不存在")
    
    # 检查是否是Excel文件上传的任务
    file_name = task_info.get("file_name", "")
    if not file_name.lower().endswith((".xlsx", ".xls")):
        raise HTTPException(status_code=400, detail="此任务不是通过Excel文件上传的，无法追加到源文件")
    
    # 获取客户端ID
    client_id = task_info.get("client_id", "unknown")
    task_type= task_info.get("task_type","DOI")
    # 构建源文件路径
    import os
    from app.core.config import settings
    source_file_path = os.path.join(settings.UPLOAD_DIR, client_id, task_type, task_id, file_name)
    
    # 检查源文件是否存在
    if not os.path.exists(source_file_path):
        raise HTTPException(status_code=404, detail="源文件不存在，请重新上传文件")
    
    # 获取DOI列表
    pattern = f"doi_task:doi_item:{task_id}:*"
    #items = redis_service.get_items_by_pattern(pattern)
    items = db_service.query_entities_as_dicts(DOIItemDB,{"task_id":task_id})
    
    # 按original_index排序
    items.sort(key=lambda item: item.get("original_index", float('inf')))
    
    # 创建一个字典，用DOI作为键，方便查找
    doi_results = {item.get("doi"): item for item in items}
    
    try:
        # 创建一个内存中的Excel文件
        output = io.BytesIO()
        
        # 使用pandas读取原始Excel文件
        import pandas as pd
        
        # 读取源文件
        df = pd.read_excel(source_file_path)
        
        # 查找DOI列
        doi_columns = [col for col in df.columns if 'doi' in str(col).lower()]
        if not doi_columns:
            raise HTTPException(status_code=400, detail="在源文件中未找到DOI列")
        
        # 使用第一个匹配的DOI列
        doi_column = doi_columns[0]
        
        # 检查是否已有状态列和网络地址列
        has_status_column = "下载状态" in df.columns
        has_url_column = "网络地址" in df.columns
        
        # 如果没有状态列，则添加
        if not has_status_column:
            df["下载状态"] = ""
        
        # 如果没有网络地址列，则添加
        if not has_url_column:
            df["网络地址"] = ""
        
        # 将结果写入DataFrame
        for i, row in df.iterrows():
            doi = str(row[doi_column]).strip()
            if doi in doi_results:
                result = doi_results[doi]
                
                # 设置状态
                status = result.get("status")
                if status == "success":
                    df.at[i, "下载状态"] = "成功"
                elif status == "error":
                    df.at[i, "下载状态"] = "失败"
                elif status == "downloading":
                    df.at[i, "下载状态"] = "下载中"
                else:
                    df.at[i, "下载状态"] = "等待中"
                
                # 设置网络地址
                df.at[i, "网络地址"] = result.get("pdf_url", "")
        
        # 创建Excel写入器
        writer = pd.ExcelWriter(output, engine='xlsxwriter')
        
        # 将DataFrame写入Excel
        df.to_excel(writer, sheet_name="DOI结果", index=False)
        
        # 获取工作表和工作簿
        workbook = writer.book
        worksheet = writer.sheets["DOI结果"]
        
        # 定义状态文本的不同格式
        success_format = workbook.add_format({'color': 'green'})  # 绿色字体
        error_format = workbook.add_format({'color': 'red'})      # 红色字体
        url_format = workbook.add_format({'color': 'blue', 'underline': 1})  # 蓝色下划线
        
        # 设置列宽
        worksheet.set_column(df.columns.get_loc(doi_column), df.columns.get_loc(doi_column), 30)  # DOI列
        worksheet.set_column(df.columns.get_loc("下载状态"), df.columns.get_loc("下载状态"), 10)  # 状态列
        worksheet.set_column(df.columns.get_loc("网络地址"), df.columns.get_loc("网络地址"), 15)  # 网络地址列
        
        # 为每一行应用格式
        for i, row in df.iterrows():
            # 状态列格式
            status_cell = df.columns.get_loc("下载状态")
            status_value = row["下载状态"]
            
            if status_value == "成功":
                worksheet.write(i + 1, status_cell, status_value, success_format)
            elif status_value == "失败":
                worksheet.write(i + 1, status_cell, status_value, error_format)
            
            # 网络地址列格式
            url_cell = df.columns.get_loc("网络地址")
            url_value = row["网络地址"]
            
            if url_value:
                worksheet.write_url(i + 1, url_cell, url_value, url_format, string="点击打开")
        
        # 保存Excel文件
        writer.close()
        
        # 将指针移到文件开头
        output.seek(0)
        
        # 返回Excel文件
        import urllib.parse
        filename = f"下载结果_{file_name}"
        encoded_filename = urllib.parse.quote(filename)
        return StreamingResponse(
            output,
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}"}
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"导出失败: {str(e)}")


@router.get("/task/{task_id}/download-pdfs")
async def download_task_pdfs(task_id: str):
    """
    打包下载指定任务的所有PDF文件
    
    此接口将指定任务ID下载的所有PDF文件打包成一个ZIP文件供用户下载。
    
    参数:
        task_id (str): 任务ID
        
    返回:
        StreamingResponse: 返回包含所有PDF文件的ZIP文件流
        
    错误:
        404: 任务不存在或没有找到PDF文件
        500: 打包过程中发生错误
    """
    # 检查任务是否存在
    task_info = db_service.get_entity_as_dict(TaskDB, task_id)
    if not task_info:
        raise HTTPException(status_code=404, detail="任务不存在")
    
    
    # 获取客户端ID
    client_id = task_info.get("client_id", "unknown")
    task_type = task_info.get("task_type", "DOI")
    file_name = os.path.splitext(task_info.get("file_name", f"PDF_{task_id}"))[0]
    # 构建下载目录路径
    type_dir = os.path.join(settings.DOWNLOAD_DIR, client_id, task_type)
    download_dir = os.path.join(type_dir, task_id)
    
    # 检查目录是否存在
    if not os.path.exists(download_dir) or not os.path.isdir(download_dir):
        raise HTTPException(status_code=404, detail="未找到下载文件目录")
    
    # 获取目录中的所有PDF文件
    pdf_files = [f for f in os.listdir(download_dir) if f.lower().endswith(".pdf")]
    
    # 检查是否有PDF文件
    if not pdf_files:
        raise HTTPException(status_code=404, detail="未找到PDF文件")
    
    try:
        # 创建一个内存中的ZIP文件
        import zipfile
        import io
        
        # 创建内存缓冲区
        zip_buffer = io.BytesIO()
        
        # 创建ZIP文件
        with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            # 添加所有PDF文件到ZIP文件
            for pdf_file in pdf_files:
                file_path = os.path.join(download_dir, pdf_file)
                # 将文件添加到ZIP文件中，使用文件名作为ZIP中的路径
                zip_file.write(file_path, pdf_file)
        
        # 将指针移到文件开头
        zip_buffer.seek(0)
        
        # 返回ZIP文件
        import urllib.parse
        c_filename = f"{file_name}.zip"
        encoded_filename = urllib.parse.quote(c_filename)
        return StreamingResponse(
            zip_buffer,
            media_type="application/zip",
            headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}"}
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"打包PDF文件失败: {str(e)}")

